#include "hlog.h"
#include "hwm.h"
#include "map.h"

static wm_t g_wm;
// The cursor position at the start of a window move/resize.
static Position drag_start_pos_;
// The position of the affected window at the start of a window
// move/resize.
static Position drag_start_frame_pos_;
// The size of the affected window at the start of a window move/resize.
static Size drag_start_frame_size_;

xcb_screen_t *screen_of_display(xcb_connection_t *c, int screen)
{
        xcb_screen_iterator_t iter;
        int screen_count;
        xcb_screen_t *scr = NULL;
        xcb_intern_atom_cookie_t cookie;
        xcb_intern_atom_reply_t *reply;
        xcb_generic_error_t *error = NULL;
        const xcb_setup_t *setup = xcb_get_setup(c);

        screen_count = xcb_setup_roots_length(setup);
        hlog("Now have %d screens", screen_count);

        iter = xcb_setup_roots_iterator(setup);
        for (; iter.rem; --screen, xcb_screen_next(&iter)) {
                if (screen == 0) {
                        cookie = xcb_intern_atom(c, 0, strlen("WM_PROTOCOLS"), "WM_PROTOCOLS");
                        reply = xcb_intern_atom_reply(c, cookie, &error);
                        if (reply == NULL) {
                                hlog("Get WM_PROTOCOLS failed: %s",
                                     xcb_errors_get_name_for_error(g_err_ctx, error->error_code, NULL));
                                free(error);
                                return scr;
                        }
                        g_wm.WM_PROTOCOLS = reply->atom;
                        free(reply);
                        reply = NULL;

                        cookie = xcb_intern_atom(c, 0, strlen("WM_DELETE_WINDOW"), "WM_DELETE_WINDOW");
                        reply = xcb_intern_atom_reply(c, cookie, &error);
                        if (reply == NULL) {
                                hlog("Get WM_DELETE_WINDOW failed: %s",
                                     xcb_errors_get_name_for_error(g_err_ctx, error->error_code, NULL));
                                free(error);
                                return scr;
                        }
                        g_wm.WM_DELETE_WINDOW = reply->atom;
                        free(reply);

                        scr = iter.data;
                        g_wm.root_ = scr->root;
                        g_wm.visual_ = scr->root_visual;
                }
        }

        return scr;
}

void on_xcb_create_notify(xcb_connection_t *c, xcb_create_notify_event_t *e)
{
        // xcb_discard_reply(c, e->sequence);
        hlog("XCB_CREATE_NOTIFY ignore");
}
void on_xcb_destroy_notify(xcb_connection_t *c, xcb_destroy_notify_event_t *e)
{
        // xcb_discard_reply(c, e->sequence);
        hlog("XCB_DESTROY_NOTIFY");
}
static int create_frame(xcb_connection_t *c, xcb_window_t w, h_bool_t is_before)
{
        // Visual properties of the frame to create.
        const unsigned int BORDER_WIDTH = 3;
        const unsigned long BORDER_COLOR = 0xff0000;
        const unsigned long BG_COLOR = 0x0000ff;
        xcb_generic_error_t *error;
        xcb_window_t frame;
        uint32_t mask = 0;
        xcb_create_window_value_list_t value_list = {.background_pixel = BG_COLOR, .border_pixel = BORDER_COLOR};
        xcb_void_cookie_t void_cookie;

        //  TODO - see Framing Existing Top-Level Windows section below.
        // frame = get_frame_by_win(w);
        if (hashmap_get(g_map, &w, sizeof(xcb_window_t), (uintptr_t *)&frame)) {  // already exists return
                return 0;
        }

        // 1. Retrieve attributes of window to frame.
        xcb_get_window_attributes_cookie_t attr_cookie = xcb_get_window_attributes(c, w);
        xcb_get_window_attributes_reply_t *attrs = xcb_get_window_attributes_reply(c, attr_cookie, &error);
        if (attrs == NULL) {
                hlog("Create frame on get attributes failed: %s",
                     xcb_errors_get_name_for_error(g_err_ctx, error->error_code, NULL));
                free(error);
                return -1;
        }
        xcb_get_geometry_cookie_t gemt_cookie = xcb_get_geometry_unchecked(c, w);
        xcb_get_geometry_reply_t *gemt = xcb_get_geometry_reply(c, gemt_cookie, &error);
        if (gemt == NULL) {
                hlog("Create frame on get geometry failed: %s",
                     xcb_errors_get_name_for_error(g_err_ctx, error->error_code, NULL));
                free(attrs);
                free(error);
                return -1;
        }

        // 2. If window was created before window manager started, we should frame
        // it only if it is visible and doesn't set override_redirect.
        if (is_before == h_true) {
                if (attrs->override_redirect || attrs->map_state != XCB_MAP_STATE_VIEWABLE) {
                        return 0;
                }
        }
        // 3. Create frame.
        frame = xcb_generate_id(c);
        mask = XCB_CW_BORDER_PIXEL | XCB_CW_BACK_PIXEL;

        void_cookie =
            xcb_create_window_aux_checked(c, XCB_COPY_FROM_PARENT, frame, g_wm.root_, gemt->x, gemt->y, gemt->width,
                                          gemt->height, BORDER_WIDTH, XCB_WINDOW_CLASS_COPY_FROM_PARENT,
                                          // screen->root_visual, mask, value_list);
                                          g_wm.visual_, mask, &value_list);
        free(attrs);
        free(gemt);
        if ((error = xcb_request_check(c, void_cookie))) {
                if (error->error_code == 8) {
                        hlog("Could not create frame match error, %s",
                             xcb_errors_get_name_for_error(g_err_ctx, error->error_code, NULL));
                } else {
                        hlog("Could not create frame, code[%s] ",
                             xcb_errors_get_name_for_error(g_err_ctx, error->error_code, NULL));
                }

                free(error);
                return -1;
        }

        // 4. Select events on frame.
        xcb_change_window_attributes_value_list_t vlist = {.event_mask = XCB_EVENT_MASK_SUBSTRUCTURE_REDIRECT |
                                                                         XCB_EVENT_MASK_SUBSTRUCTURE_NOTIFY};
        void_cookie = xcb_change_window_attributes_aux_checked(c, frame, XCB_CW_EVENT_MASK, &vlist);
        if ((error = xcb_request_check(c, void_cookie))) {
                hlog("Could not create frame on change attributes, code[%s] ",
                     xcb_errors_get_name_for_error(g_err_ctx, error->error_code, NULL));
                free(error);
                return -1;
        }
        // 5. Add client to save set, so that it will be restored and kept alive if we
        // crash.
        void_cookie = xcb_change_save_set_checked(c, XCB_SET_MODE_INSERT, w);
        if ((error = xcb_request_check(c, void_cookie))) {
                hlog("Could not create frame on save change, code[%s] ",
                     xcb_errors_get_name_for_error(g_err_ctx, error->error_code, NULL));
                free(error);
                return -1;
        }
        // 6. Reparent client window.
        void_cookie = xcb_reparent_window_checked(c, w, frame, 0, 0);  // Offset of client window within frame.
        if ((error = xcb_request_check(c, void_cookie))) {
                hlog("Could not create frame on reparent, code[%s] ",
                     xcb_errors_get_name_for_error(g_err_ctx, error->error_code, NULL));
                free(error);
                return -1;
        }

        // 7. Map frame.
        void_cookie = xcb_map_window_checked(c, frame);
        if ((error = xcb_request_check(c, void_cookie))) {
                hlog("Could not create frame on map, code[%s] ",
                     xcb_errors_get_name_for_error(g_err_ctx, error->error_code, NULL));
                free(error);
                return -1;
        }

        // 8. Save frame handle.
        if (hashmap_set(g_map, &w, sizeof(xcb_window_t), frame) == -1) {
                hlog("Set frame and window pairs failed: %s", strerror(errno));
                return -1;
        }

        // 9. Grab universal window management actions on client window.
        //   a. Move windows with alt + left button.
        mask = XCB_EVENT_MASK_BUTTON_PRESS | XCB_EVENT_MASK_BUTTON_RELEASE | XCB_EVENT_MASK_BUTTON_MOTION;
        void_cookie = xcb_grab_button_checked(c, 0, w, mask, XCB_GRAB_MODE_ASYNC, XCB_GRAB_MODE_ASYNC, XCB_NONE,
                                              XCB_NONE, XCB_BUTTON_MASK_1, XCB_MOD_MASK_1);
        if ((error = xcb_request_check(c, void_cookie))) {
                hlog("Bind alt + left button failed, code[%s] ",
                     xcb_errors_get_name_for_error(g_err_ctx, error->error_code, NULL));
                free(error);
                return -1;
        }

        //   b. Resize windows with alt + right button.
        mask = XCB_EVENT_MASK_BUTTON_PRESS | XCB_EVENT_MASK_BUTTON_RELEASE | XCB_EVENT_MASK_BUTTON_MOTION;
        void_cookie = xcb_grab_button_checked(c, 0, w, mask, XCB_GRAB_MODE_ASYNC, XCB_GRAB_MODE_ASYNC, XCB_NONE,
                                              XCB_NONE, XCB_BUTTON_MASK_3, XCB_MOD_MASK_1);
        if ((error = xcb_request_check(c, void_cookie))) {
                hlog("Bind alt + right button failed, code[%s] ",
                     xcb_errors_get_name_for_error(g_err_ctx, error->error_code, NULL));
                free(error);
                return -1;
        }

        xcb_key_symbols_t *sym = xcb_key_symbols_alloc(c);
        if (sym == NULL) {
                hlog("Alloc key symbols failed, code[%s] ", strerror(errno));
                return -1;
        }
        //   c. Kill windows with alt + f4.
        xcb_keycode_t f4 = *xcb_key_symbols_get_keycode(sym, XK_F4);
        xcb_keycode_t tab = *xcb_key_symbols_get_keycode(sym, XK_Tab);
        xcb_key_symbols_free(sym);
        void_cookie = xcb_grab_key_checked(c, 0, w, XCB_MOD_MASK_1, f4, XCB_GRAB_MODE_ASYNC, XCB_GRAB_MODE_ASYNC);
        if ((error = xcb_request_check(c, void_cookie))) {
                hlog("Bind alt + f4 key failed, code[%s] ",
                     xcb_errors_get_name_for_error(g_err_ctx, error->error_code, NULL));
                free(error);
                return -1;
        }

        //   d. Switch windows with alt + tab.
        void_cookie = xcb_grab_key_checked(c, 0, w, XCB_MOD_MASK_1, tab, XCB_GRAB_MODE_ASYNC, XCB_GRAB_MODE_ASYNC);
        if ((error = xcb_request_check(c, void_cookie))) {
                hlog("Bind alt + tab key failed, code[%s] ",
                     xcb_errors_get_name_for_error(g_err_ctx, error->error_code, NULL));
                free(error);
                return -1;
        }

        hlog("Framed window:%08x  [%08x]", w, frame);
        return 0;
}
static int unmap_frame(xcb_connection_t *c, xcb_window_t frame, xcb_window_t w)
{
        xcb_void_cookie_t void_cookie;
        xcb_generic_error_t *error;

        // 1. Unmap frame.
        void_cookie = xcb_unmap_window_checked(c, frame);
        hlog("Unmap a frame");
        if ((error = xcb_request_check(c, void_cookie))) {
                hlog("Unmap frame[%u] from window[%u] failed: %s", frame, w,
                     xcb_errors_get_name_for_error(g_err_ctx, error->error_code, NULL));
                free(error);
                return -1;
        }

        // 2. Reparent client window back to root window.
        void_cookie = xcb_reparent_window_checked(c, w, g_wm.root_, 0, 0);  // Offset of client window within root.
        if ((error = xcb_request_check(c, void_cookie))) {
                hlog("Unmap reparent frame[%u] from window[%u] failed: %s", frame, w,
                     xcb_errors_get_name_for_error(g_err_ctx, error->error_code, NULL));
                free(error);
                return -1;
        }

        // 3. Remove client window from save set, as it is now unrelated to us.
        void_cookie = xcb_change_save_set_checked(c, XCB_SET_MODE_DELETE, w);
        if ((error = xcb_request_check(c, void_cookie))) {
                hlog("Unmap client %u on delete change failed: [%s]", w,
                     xcb_errors_get_name_for_error(g_err_ctx, error->error_code, NULL));
                free(error);
                return -1;
        }

        // 4. Destroy frame.
        void_cookie = xcb_destroy_window_checked(c, frame);
        if ((error = xcb_request_check(c, void_cookie))) {
                hlog("Destroy frame[%u] failed: %s", frame,
                     xcb_errors_get_name_for_error(g_err_ctx, error->error_code, NULL));
                free(error);
                return -1;
        }

        // 5. Drop reference to frame handle.
        hashmap_remove(g_map, &w, sizeof(xcb_window_t));
        return 0;
}
void on_xcb_unmap_notify(xcb_connection_t *c, xcb_unmap_notify_event_t *e)
{
        xcb_window_t frame;
        int ret;

        ret = hashmap_get(g_map, &e->window, sizeof(xcb_window_t), (uintptr_t *)&frame);
        if (ret == 0) {
                hlog("Ignore XCB_UNMAP_NOTIFY for non-client window %u", e->window);
                return;
        }
        if (e->event == g_wm.root_) {
                hlog("Ignore UnmapNotify for reparented pre-existing window %u", e->window);
                return;
        }
        unmap_frame(c, frame, e->window);
}

void on_xcb_map_notify(xcb_connection_t *c, xcb_map_notify_event_t *e)
{
        // xcb_discard_reply(c, e->sequence);
        hlog("XCB_MAP_NOTIFY ignore");
}
void on_xcb_map_request(xcb_connection_t *c, xcb_map_request_event_t *e)
{
        if (create_frame(c, e->window, h_false)) {
                return;
        }
        xcb_map_window(c, e->window);
}
void on_xcb_reparent_notify(xcb_connection_t *c, xcb_reparent_notify_event_t *e)
{
        // xcb_discard_reply(c, e->sequence);
        hlog("XCB_REPARENT_NOTIFY ignore");
}
void on_xcb_configure_notify(xcb_connection_t *c, xcb_configure_notify_event_t *e)
{
        // xcb_discard_reply(c, e->sequence);
        hlog("XCB_CONFIGURE_NOTIFY ignore");
}
void on_xcb_configure_request(xcb_connection_t *c, xcb_configure_request_event_t *e)
{
        xcb_window_t frame;
        xcb_void_cookie_t cookie;
        xcb_generic_error_t *error;
        int ret;
        uint16_t mask = 0;
        xcb_configure_window_value_list_t changes = {.x = e->x,
                                                     .y = e->y,
                                                     .width = e->width,
                                                     .height = e->height,
                                                     .border_width = e->border_width,
                                                     .sibling = e->parent,
                                                     .stack_mode = e->stack_mode};

        // Copy fields from e to changes.
        mask |= XCB_CONFIG_WINDOW_X;
        mask |= XCB_CONFIG_WINDOW_Y;
        mask |= XCB_CONFIG_WINDOW_WIDTH;
        mask |= XCB_CONFIG_WINDOW_HEIGHT;
        mask |= XCB_CONFIG_WINDOW_BORDER_WIDTH;
        mask |= XCB_CONFIG_WINDOW_SIBLING;
        mask |= XCB_CONFIG_WINDOW_STACK_MODE;

        // Grant request by calling XConfigureWindow().
        // ret = get_frame_by_win(e->window);
        ret = hashmap_get(g_map, &e->window, sizeof(xcb_window_t), (uintptr_t *)&frame);
        if (ret) {
                // XConfigureWindow(display_, frame, e.value_mask, &changes);
                hlog("Resize frame [%u], width:%hu, height:%hu", frame, e->width, e->height);
                cookie = xcb_configure_window_aux_checked(c, frame, mask, &changes);
                if ((error = xcb_request_check(c, cookie))) {
                        hlog("Resize frame[%u] failed: %s", frame,
                             xcb_errors_get_name_for_error(g_err_ctx, error->error_code, NULL));
                        free(error);
                        return;
                }
        }
        hlog("Window[%d] resize  to width:%d, height:%d", e->window, e->width, e->height);
        cookie = xcb_configure_window_aux_checked(c, e->window, e->value_mask, &changes);
        if ((error = xcb_request_check(c, cookie))) {
                hlog("Resize window[%u] failed: %s", e->window,
                     xcb_errors_get_name_for_error(g_err_ctx, error->error_code, NULL));
                free(error);
                return;
        }
}

void on_xcb_mapping_notify(xcb_connection_t *c, xcb_mapping_notify_event_t *e)
{
        xcb_discard_reply(c, e->sequence);
        hlog("XCB_MAPPING_NOTIFY ignore");
}

void on_xcb_button_press(xcb_connection_t *c, xcb_button_press_event_t *e)
{
        xcb_window_t frame;
        int x, y;
        unsigned width, height, border_width, depth;
        xcb_generic_error_t *error;
        xcb_window_t returned_root;
        const xcb_configure_window_value_list_t values = {.stack_mode = XCB_STACK_MODE_ABOVE};
        xcb_void_cookie_t cookie;

        if (hashmap_get(g_map, &e->event, sizeof(xcb_window_t), (uintptr_t *)&frame)) {
                hlog("Error: not found frame window on button press");
                exit(EXIT_FAILURE);
        }

        // 1. Save initial cursor position.
        drag_start_pos_.x = e->root_x;
        drag_start_pos_.y = e->root_y;

        // 2. Save initial window info.
        xcb_get_geometry_cookie_t gemt_cookie = xcb_get_geometry_unchecked(c, frame);
        xcb_get_geometry_reply_t *gemt = xcb_get_geometry_reply(c, gemt_cookie, &error);
        if (gemt == NULL) {
                hlog("Get geometry on button press failed: %s",
                     xcb_errors_get_name_for_error(g_err_ctx, error->error_code, NULL));
                free(error);
                exit(EXIT_FAILURE);
        }

        returned_root = gemt->root;
        x = gemt->x;
        y = gemt->y;
        width = gemt->width;
        height = gemt->height;
        border_width = gemt->border_width;
        depth = gemt->depth;

        drag_start_frame_pos_.x = x;
        drag_start_frame_pos_.y = y;
        drag_start_frame_size_.w = width;
        drag_start_frame_size_.h = height;
        free(gemt);
        // 3. Raise clicked window to top.

        /* The connection c and the window win are supposed to be defined */
        /* Move the window on the top of the stack */
        cookie = xcb_configure_window_aux_checked(c, frame, XCB_CONFIG_WINDOW_STACK_MODE, &values);
        if ((error = xcb_request_check(c, cookie))) {
                hlog("Raise frame[%u] failed: %s", frame,
                     xcb_errors_get_name_for_error(g_err_ctx, error->error_code, NULL));
                free(error);
        }
}

void on_xcb_button_release(xcb_connection_t *c, xcb_button_release_event_t *e) {}

void on_motion_notify(xcb_connection_t *c, xcb_motion_notify_event_t *e)
{
        xcb_window_t frame;
        xcb_void_cookie_t cookie;
        xcb_generic_error_t *error;

        // CHECK(clients_.count(e.window));
        if (!hashmap_get(g_map, &e->event, sizeof(xcb_window_t), (uintptr_t *)&frame)) {
                hlog("Not found window[%u] on motion notify", e->event);
                exit(EXIT_FAILURE);
        }
        const Position drag_pos = {e->root_x, e->root_y};
        const Vector2D delta = {drag_pos.x - drag_start_pos_.x, drag_pos.y - drag_start_pos_.y};

        if (e->state & XCB_BUTTON_MASK_1) {
                // alt + left button: Move window.
                const Position dest_frame_pos = {drag_start_frame_pos_.x + delta.x, drag_start_frame_pos_.y + delta.y};
                xcb_configure_window_value_list_t values = {.x = dest_frame_pos.x, .y = dest_frame_pos.y};
                cookie = xcb_configure_window_aux_checked(c, frame, XCB_CONFIG_WINDOW_X | XCB_CONFIG_WINDOW_Y, &values);
                if ((error = xcb_request_check(c, cookie))) {
                        hlog("Raise frame[%u] failed: %s", frame,
                             xcb_errors_get_name_for_error(g_err_ctx, error->error_code, NULL));
                        free(error);
                }
        } else if (e->state & XCB_BUTTON_MASK_3) {
                // alt + right button: Resize window.
                // Window dimensions cannot be negative.
                const Vector2D size_delta = {MAX(delta.x, -drag_start_frame_size_.w),
                                             MAX(delta.y, -drag_start_frame_size_.h)};
                const Size dest_frame_size = {drag_start_frame_size_.w + size_delta.x,
                                              drag_start_frame_size_.h + size_delta.y};
                xcb_configure_window_value_list_t values = {.width = dest_frame_size.w, .height = dest_frame_size.h};
                // 1. Resize frame.
                cookie = xcb_configure_window_aux_checked(c, frame, XCB_CONFIG_WINDOW_WIDTH | XCB_CONFIG_WINDOW_HEIGHT,
                                                          &values);
                if ((error = xcb_request_check(c, cookie))) {
                        hlog("Raise frame[%u] failed: %s", frame,
                             xcb_errors_get_name_for_error(g_err_ctx, error->error_code, NULL));
                        free(error);
                        return;
                }
                // 2. Resize client window.
                cookie = xcb_configure_window_aux_checked(c, e->event,
                                                          XCB_CONFIG_WINDOW_WIDTH | XCB_CONFIG_WINDOW_HEIGHT, &values);
                if ((error = xcb_request_check(c, cookie))) {
                        hlog("Raise frame[%u] failed: %s", frame,
                             xcb_errors_get_name_for_error(g_err_ctx, error->error_code, NULL));
                        free(error);
                }
        }
}

static int match_protocols(const xcb_icccm_get_wm_protocols_reply_t *proto, xcb_atom_t some)
{
        unsigned int i;
        for (i = 0; i < proto->atoms_len; i++) {
                if (proto->atoms[i] == some) {
                        return 1;
                }
        }

        return 0;
}

void on_xcb_key_press(xcb_connection_t *c, xcb_key_press_event_t *e)
{
        xcb_generic_error_t *error;
        xcb_void_cookie_t cookie;
        xcb_key_symbols_t *sym = xcb_key_symbols_alloc(c);
        if (sym == NULL) {
                hlog("Alloc key symbols on key press failed, code[%s] ", strerror(errno));
                return;
        }
        xcb_keycode_t f4 = *xcb_key_symbols_get_keycode(sym, XK_F4);
        xcb_keycode_t tab = *xcb_key_symbols_get_keycode(sym, XK_Tab);
        xcb_key_symbols_free(sym);
        if ((e->state & XCB_MOD_MASK_1) && (e->detail == f4)) {
                // alt + f4: Close window.
                //
                // There are two ways to tell an X window to close. The first is to send it
                // a message of type WM_PROTOCOLS and value WM_DELETE_WINDOW. If the client
                // has not explicitly marked itself as supporting this more civilized
                // behavior (using XSetWMProtocols()), we kill it with XKillClient().
                int is_succ = 0;
                xcb_atom_t *supported_protocols;
                int num_supported_protocols;
                xcb_icccm_get_wm_protocols_reply_t reply;
                xcb_get_property_cookie_t pcookie = xcb_icccm_get_wm_protocols(c, e->event, g_wm.WM_PROTOCOLS);
                if (!xcb_icccm_get_wm_protocols_reply(c, pcookie, &reply, &error)) {
                        hlog("Get protocols window[%u] failed: %s", e->event,
                             xcb_errors_get_name_for_error(g_err_ctx, error->error_code, NULL));
                        free(error);

                } else {
                        int match = match_protocols(&reply, g_wm.WM_DELETE_WINDOW);
                        xcb_icccm_get_wm_protocols_reply_wipe(&reply);
                        if (match) {
                                hlog("Gracefully deleting window[%u]", e->event);
                                xcb_client_message_event_t client;
                                client.response_type = XCB_CLIENT_MESSAGE;
                                client.type = g_wm.WM_PROTOCOLS;
                                client.window = e->event;
                                client.format = 32;
                                client.data.data32[0] = g_wm.WM_DELETE_WINDOW;
                                // 1. Construct message.
                                cookie = xcb_send_event_checked(c, 0, e->event, 0, (char *)&client);
                                if ((error = xcb_request_check(c, cookie))) {
                                        hlog("Send client message window[%u] failed: %s", e->event,
                                             xcb_errors_get_name_for_error(g_err_ctx, error->error_code, NULL));
                                        free(error);
                                } else {
                                        is_succ = 1;
                                }
                        }
                }
                if (!is_succ) {
                        hlog("Killing window [%u]", e->event);
                        cookie=xcb_kill_client_checked(c, e->event);
                        if ((error = xcb_request_check(c, cookie))) {
                                hlog("Kill client window[%u] failed: %s", e->event,
                                     xcb_errors_get_name_for_error(g_err_ctx, error->error_code, NULL));
                                free(error);
                        }
                }
        } else if ((e->state & XCB_MOD_MASK_1) &&
e->detail==tab) {
                // alt + tab: Switch window.
                // 1. Find next window.
        }
#if 0
else if ((e.state & Mod1Mask) &&
             (e.keycode == XKeysymToKeycode(display_, XK_Tab))) {
    // alt + tab: Switch window.
    // 1. Find next window.
    auto i = clients_.find(e.window);
    CHECK(i != clients_.end());
    ++i;
    if (i == clients_.end()) {
      i = clients_.begin();
    }
    // 2. Raise and set focus.
    XRaiseWindow(display_, i->second);
    XSetInputFocus(display_, i->first, RevertToPointerRoot, CurrentTime);
  }
#endif
}
void run(xcb_connection_t *c, int nbr)
{
        xcb_generic_error_t *error;
        xcb_screen_t *screen = NULL;
        xcb_generic_event_t *e;
        xcb_window_t returned_root;
        xcb_window_t *top_level_windows;
        xcb_query_tree_cookie_t query_cookie;
        xcb_query_tree_reply_t *query_reply;
        uint32_t mask;
        xcb_void_cookie_t cookie;
        int i;

        screen = screen_of_display(c, nbr);
        if (screen == NULL) {
                hlog("Not default screen found.");
                return;
        }

        // xcb version
        mask = XCB_EVENT_MASK_SUBSTRUCTURE_REDIRECT | XCB_EVENT_MASK_SUBSTRUCTURE_NOTIFY;
        cookie = xcb_change_window_attributes_checked(c, g_wm.root_, XCB_CW_EVENT_MASK, &mask);
        if ((error = xcb_request_check(c, cookie))) {
                hlog("Could not reparent the window, code[%s]",
                     xcb_errors_get_name_for_error(g_err_ctx, error->error_code, NULL));
                free(error);
                return;
        }

        //   c. Grab X server to prevent windows from changing under us while we
        //   frame them.
        cookie = xcb_grab_server_checked(c);
        if ((error = xcb_request_check(c, cookie))) {
                hlog("Could not grab server, code[%s] ",
                     xcb_errors_get_name_for_error(g_err_ctx, error->error_code, NULL));
                free(error);
                return;
        }

        //   d. Frame existing top-level windows.
        //     i. Query existing top-level windows.
        query_cookie = xcb_query_tree(c, g_wm.root_);
        query_reply = xcb_query_tree_reply(c, query_cookie, &error);
        if (!query_reply) {
                hlog("Could not query tree, code[%s] ",
                     xcb_errors_get_name_for_error(g_err_ctx, error->error_code, NULL));
                free(error);
                return;
        }
        returned_root = query_reply->root;
        if (returned_root != g_wm.root_) {
                free(query_reply);
                return;
        }
        top_level_windows = xcb_query_tree_children(query_reply);
        for (i = 0; i < xcb_query_tree_children_length(query_reply); ++i) {
                /* was_created_before_window_manager */
                hlog("Exist window[%u] create frame", top_level_windows[i]);
                create_frame(c, top_level_windows[i], h_true);
        }
        //     iii. Free top-level window array.
        free(query_reply);

        //   e. Ungrab X server.
        cookie = xcb_ungrab_server_checked(c);
        if ((error = xcb_request_check(c, cookie))) {
                hlog("Could not ungrab server, code[%s] ",
                     xcb_errors_get_name_for_error(g_err_ctx, error->error_code, NULL));
                free(error);
                return;
        }
        while (1) {
                e = xcb_wait_for_event(c);
                if (e) {
                        switch (e->response_type & 0x7f) {
                        case XCB_KEY_PRESS:
                                on_xcb_key_press(c, (xcb_key_press_event_t *)e);
                                break;
                        case XCB_KEY_RELEASE:

                                break;
                        case XCB_BUTTON_PRESS:
                                on_xcb_button_press(c, (xcb_button_press_event_t *)e);
                                break;
                        case XCB_BUTTON_RELEASE:
                                on_xcb_button_release(c, (xcb_button_release_event_t *)e);
                                break;
                        case XCB_CREATE_NOTIFY:
                                on_xcb_create_notify(c, (xcb_create_notify_event_t *)e);
                                break;
                        case XCB_MOTION_NOTIFY:
                                // Skip any already pending motion events.
                                // xcb没有队列需自己实现
                                //     while (
                                //         xcb_typed_window_event(c, ((xcb_motion_notify_event_t *)e)->event,
                                //         XCB_MOTION_NOTIFY, &e)) {
                                //     }
                                on_motion_notify(c, (xcb_motion_notify_event_t *)e);
                                break;
                        case XCB_DESTROY_NOTIFY:
                                on_xcb_destroy_notify(c, (xcb_destroy_notify_event_t *)e);
                                break;

                        case XCB_UNMAP_NOTIFY:
                                on_xcb_unmap_notify(c, (xcb_unmap_notify_event_t *)e);
                                break;

                        case XCB_MAP_NOTIFY:
                                on_xcb_map_notify(c, (xcb_map_notify_event_t *)e);
                                break;

                        case XCB_MAP_REQUEST:
                                on_xcb_map_request(c, (xcb_map_request_event_t *)e);
                                break;

                        case XCB_REPARENT_NOTIFY:
                                on_xcb_reparent_notify(c, (xcb_reparent_notify_event_t *)e);
                                break;

                        case XCB_CONFIGURE_REQUEST:
                                on_xcb_configure_request(c, (xcb_configure_request_event_t *)e);
                                break;

                        case XCB_CONFIGURE_NOTIFY:
                                on_xcb_configure_notify(c, (xcb_configure_notify_event_t *)e);
                                break;

                        case XCB_MAPPING_NOTIFY:
                                on_xcb_mapping_notify(c, (xcb_mapping_notify_event_t *)e);
                                break;

                        default:
                                if (e->response_type == 0) {
                                        hlog("Recieve error: %s",
                                             xcb_errors_get_name_for_xcb_event(g_err_ctx, e, NULL));
                                } else {
                                        hlog("Ignore %03d", e->response_type & ~0x80);
                                }
                                break;
                        }

                        free(e);
                } else {
                        hlog("Recieve error: server shutdown");
                        break;
                }
        }
}
